package com.chang.order.service.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chang.common.exception.NoStockException;
import com.chang.common.to.mq.SeckillOrderTo;
import com.chang.common.utils.PageUtils;
import com.chang.common.utils.Query;
import com.chang.common.utils.R;
import com.chang.common.vo.MemberResponseVo;
import com.chang.order.constant.OrderConstant;
import com.chang.order.constant.PayConstant;
import com.chang.order.entity.OrderEntity;
import com.chang.order.entity.OrderItemEntity;
import com.chang.order.entity.PaymentInfoEntity;
import com.chang.order.enume.OrderStatusEnum;
import com.chang.order.feign.CartFeignService;
import com.chang.order.feign.MemberFeignService;
import com.chang.order.feign.ProductFeignService;
import com.chang.order.feign.WareFeignService;
import com.chang.order.interceptor.LoginUserInterceptor;
import com.chang.order.mapper.OrderDao;
import com.chang.order.service.OrderItemService;
import com.chang.order.service.OrderService;
import com.chang.order.service.PaymentInfoService;
import com.chang.order.to.OrderCreateTo;
import com.chang.order.to.SpuInfoVo;
import com.chang.order.utils.redis.RedisUtils;
import com.chang.order.vo.*;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {

    private ThreadLocal<OrderSubmitVo> orderSubmitVothreadLocal = new ThreadLocal<>();


    @Resource
    OrderItemService orderItemService;

    @Resource
    MemberFeignService memberFeignService;

    @Resource
    CartFeignService cartFeignService;

    @Resource
    WareFeignService wareFeignService;

    @Resource
    RabbitTemplate rabbitTemplate;

    @Resource
    ThreadPoolExecutor threadPoolExecutor;

    @Resource
    ProductFeignService productFeignService;

    @Resource
    PaymentInfoService paymentInfoService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public OrderConfirmVo confirmOrder() {
        MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
        log.info("获取用户信息memberResponseVo=>{}", memberResponseVo);
        OrderConfirmVo orderConfirm = new OrderConfirmVo();
        // 异步使用feign远程调用时开启的新线程会无法使用原本的本地线程,也就无法使用原本的feign拦截器
        // 所以就需要把把主线程的请求上下文复制到子线程中
        RequestAttributes attributes = RequestContextHolder.getRequestAttributes();

        // 每一个异步线程调用拦截器
        CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            // 远程查询用户的地址列表
            List<MemberAddressVo> addressList = memberFeignService.getAddressList(memberResponseVo.getId());
            log.info("获取用户地址列表=>{}", addressList.toString());
            orderConfirm.setMemberAddressVos(addressList);
        }, threadPoolExecutor);


        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            // 远程查询购物车中的所有选中的购物项
            List<OrderItemVo> orderItems = cartFeignService.currentUserCartItems();
            orderConfirm.setItems(orderItems);
        }, threadPoolExecutor).thenRunAsync(() -> {
            RequestContextHolder.setRequestAttributes(attributes);
            List<OrderItemVo> orderItems = orderConfirm.getItems();
            orderItems = updateSkuStock(orderItems);
            orderConfirm.setItems(orderItems);
        }, threadPoolExecutor);

        // feign在远程调用之前会构造请求,调用很多的拦截器,而默认构造的新的请求是没有请求头的,自然也就没有请求头携带的cookie,进而没有cookie中的jsessionId.自然也就无法获取数据库中的共享session信息,以及储存的用户信息
        // 而这样又被购物车的请求拦截器拦截,以为是临时用户,进而就无法获取原本用户的购物车信息了
        // 查询用户积分
        Integer integration = memberResponseVo.getIntegration();
        orderConfirm.setIntegration(integration);

        // 防重令牌 三十分钟过期
        String token = RandomUtil.randomNumbers(12);
        RedisUtils.set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberResponseVo.getId(), token, 30, TimeUnit.MINUTES);
        orderConfirm.setOrderToken(token);

        try {
            CompletableFuture.allOf(getAddressFuture, cartFuture).get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
        log.info("OrderConfirmVo=>{}", orderConfirm);


        return orderConfirm;
    }

    public List<OrderItemVo> updateSkuStock(List<OrderItemVo> orderItemVos) {
        List<Long> skuIds = new ArrayList<>();
        orderItemVos.forEach(orderItemVo -> {
            skuIds.add(orderItemVo.getSkuId());
        });
        Map<Long, Integer> batchStock = wareFeignService.getBatchStock(skuIds);
        orderItemVos.forEach(orderItemVo -> {
            // 库存必须有这件商品,并且查出来的库存大于订单库存才能显示为有货
            if (batchStock.containsKey(orderItemVo.getSkuId())) {
                orderItemVo.setHasStock(batchStock.get(orderItemVo.getSkuId()) - orderItemVo.getCount() > 0);
            }
        });
        return orderItemVos;
    }


    // 本地事务在分布式环境下只能控制本地回滚 分布式环境需要分布式事务
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    @Override
    public SubmitOrderResponseVo submitOrder(OrderSubmitVo orderSubmitVo) {
        // 初始化订单
        SubmitOrderResponseVo orderResponseVo = new SubmitOrderResponseVo();

        orderResponseVo.setCode(0);

        orderSubmitVothreadLocal.set(orderSubmitVo);

        // 从拦截器获取用户信息
        MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
        String orderToken = orderSubmitVo.getOrderToken();
        log.info("orderToken=>{}", orderToken);


        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        // 原子性操作 验证成功之后删锁
        Long redisResult = RedisUtils.executeLuaScript(script, OrderConstant.USER_ORDER_TOKEN_PREFIX + memberResponseVo.getId(), orderToken);
        log.info("redisResult=>{}", redisResult);
        if (redisResult == 1L) {
            log.info("购物车令牌验证成功");
            // 验证成功
            // 下单：创建订单
            OrderCreateTo orderTo = createOrder();
            // 验价
            BigDecimal payAmount = orderTo.getOrder().getPayAmount();
            BigDecimal payPrice = orderSubmitVo.getPayPrice();
            // 如果两个价格相减之后的绝对值<0.01 就认为验价成功
            if (Math.abs(payAmount.subtract(payPrice).doubleValue()) < 0.01) {
                log.info("购物车与实际订单金额对比成功...");

                //金额对比成功 保存订单
                saveOrder(orderTo);
                // 锁定库存 如果有异常,回滚订单数据
                WareSkuLockVo lockVo = new WareSkuLockVo();
                lockVo.setOrderSn(orderTo.getOrder().getOrderSn());

                // 直接获取
                List<OrderItemVo> list = orderTo.getOrderItems().stream().map(item -> {
                    OrderItemVo orderItemVo = new OrderItemVo();
                    orderItemVo.setSkuId(item.getSkuId());
                    orderItemVo.setTitle(item.getSkuName());
                    orderItemVo.setCount(item.getSkuQuantity());
                    return orderItemVo;
                }).toList();
                log.info("库存待锁定..{}", list);
                lockVo.setLocks(list);
                // TODO 远程锁库存
                R wareResult = wareFeignService.orderLockStock(lockVo);
                if (wareResult.getCode() == 0) {
                    // 库存锁定成功
                    orderResponseVo.setOrder(orderTo.getOrder());
                    log.info("订单库存锁定成功=> {}", orderResponseVo);
                    // int i = 1 / 0;
                    // 发送消息给mq
                    rabbitTemplate.convertAndSend("order-event-exchange", "order.create", orderTo.getOrder());
                } else {
                    // 库存锁定失败
                    orderResponseVo.setCode(3);
                    log.info("订单库存锁定失败=> {}", orderResponseVo);
                    throw new NoStockException("库存锁定失败");
                }
                return orderResponseVo;

            } else {
                orderResponseVo.setCode(2);
                return orderResponseVo;
            }

        } else {
            orderResponseVo.setCode(1);
            return orderResponseVo;
        }

    }

    // 保存订单数据
    private void saveOrder(OrderCreateTo orderTo) {
        OrderEntity order = orderTo.getOrder();
        order.setModifyTime(new Date());
        this.save(order);

        List<OrderItemEntity> orderItems = orderTo.getOrderItems();
        orderItemService.saveBatch(orderItems);  // 批量保存订单信息
    }

    // 创建订单
    private OrderCreateTo createOrder() {

        // 生成订单号 创建订单
        OrderCreateTo orderCreateTo = new OrderCreateTo();
        String orderSn = IdWorker.getTimeId().substring(0, 16); // IdWorker生成唯一订单id,不能过长
        OrderEntity orderEntity = buildOrder(orderSn);
        // 获取到所有的订单项
        List<OrderItemEntity> orderItems = buildOrderItems(orderSn);

        // 验价 计算价格,积分等相关
        computePrice(orderEntity, orderItems);
        orderCreateTo.setOrder(orderEntity);
        orderCreateTo.setOrderItems(orderItems);

        return orderCreateTo;
    }

    // 统计价格信息
    private void computePrice(OrderEntity orderEntity, List<OrderItemEntity> orderItems) {
        // 订单价格相关
        BigDecimal total = new BigDecimal("0.0");

        // 总优惠券金额
        BigDecimal coupon = new BigDecimal("0.0");

        // 总积分优惠金额
        BigDecimal integration = new BigDecimal("0.0");

        // 总打折优惠金额
        BigDecimal promotion = new BigDecimal("0.0");

        // 总积分
        BigDecimal GiftGrowth = new BigDecimal("0.0");

        // 总成长值
        BigDecimal GiftIntegration = new BigDecimal("0.0");

        // 订单的总额就是叠加每一个订单项目的总额信息
        for (OrderItemEntity orderItem : orderItems) {
            // 单价*数量计算单个订单项的总价 比如买了两条同款裤子  这里计算实际金额
            BigDecimal realAmount = orderItem.getRealAmount();
            // 单个订单项优惠券金额
            coupon = coupon.add(orderItem.getCouponAmount());
            // 积分优惠金额
            integration = integration.add(orderItem.getIntegrationAmount());
            // 打折优惠金额
            promotion = promotion.add(orderItem.getPromotionAmount());
            total = total.add(realAmount);

            // 统计所有购物项成长值
            GiftGrowth = GiftGrowth.add(new BigDecimal(orderItem.getGiftGrowth()));
            // 统计所有购物项积分
            GiftIntegration = GiftIntegration.add(new BigDecimal(orderItem.getGiftIntegration()));
        }
        // 订单价格相关
        orderEntity.setTotalAmount(total);
        log.info("订单总额为: {}", total);
        // 同样随机运费 如果两边都随机运费的话会影响验价,所以运费固定为5
        // BigDecimal fare = new BigDecimal(RandomUtil.randomInt(1, 20));
        BigDecimal fare = new BigDecimal(5);
        // 应付总额
        orderEntity.setPayAmount(total.add(fare));
        log.info("订单应付总额为: {}", orderEntity.getPayAmount());
        // 统计全部折扣
        orderEntity.setPromotionAmount(promotion);
        orderEntity.setIntegrationAmount(integration);
        orderEntity.setCouponAmount(coupon);

        // 封装订单本身产生的积分信息和会员成长信息
        orderEntity.setIntegration(GiftIntegration.intValue());
        orderEntity.setGrowth(GiftGrowth.intValue());

        orderEntity.setDeleteStatus(0); // 订单删除状态,1代表删除


    }

    // 构建所有订单项数据
    // 获取所有得订单项 这里先获取购物车中的数据 然后再将购物车中的数据封装成最后的订单项
    // 因为购物车的数据是在调用接口之后从redis展现出来的数据,但是如果在购物车页面选中或放弃构想项目时，价格不会实时更新
    private List<OrderItemEntity> buildOrderItems(String orderSn) {
        List<OrderItemVo> orderItemVos = cartFeignService.currentUserCartItems();
        log.info("获取用户订单: {}", orderItemVos);
        if (orderItemVos != null && !orderItemVos.isEmpty()) {
            List<OrderItemEntity> OrderItemlist = orderItemVos.stream().map(cartItem -> {
                // 循环构建每一个订单项 这里比较难受的就是依然是循环调远程查库
                OrderItemEntity orderItem = buildOrderItem(cartItem);
                orderItem.setOrderSn(orderSn);
                return orderItem;
            }).toList();
            return OrderItemlist;
        }
        return null;
    }


    // 构建某一个订单项
    private OrderItemEntity buildOrderItem(OrderItemVo cartItem) {
        OrderItemEntity orderItemEntity = new OrderItemEntity();
        //  商品spu信息
        Long skuId = cartItem.getSkuId();
        R result = productFeignService.getSpuInfoBySkuId(skuId);
        SpuInfoVo spuInfoVo = JSONUtil.toBean(result.getData(), SpuInfoVo.class);
        orderItemEntity.setSpuBrand(spuInfoVo.getBrandName());
        orderItemEntity.setSpuName(spuInfoVo.getSpuName());
        orderItemEntity.setCategoryId(spuInfoVo.getCatalogId());
        // 商品sku信息
        orderItemEntity.setSkuId(skuId);
        orderItemEntity.setSkuName(cartItem.getTitle());
        orderItemEntity.setSkuPic(cartItem.getImage());
        orderItemEntity.setSkuPrice(cartItem.getPrice());
        List<String> skuAttrValues = cartItem.getSkuAttrValues();
        // 将集合的各个元素拼接为一整个字符串,并用指定字符间隔
        String skuAttr = StringUtils.collectionToDelimitedString(skuAttrValues, ";");
        orderItemEntity.setSkuAttrsVals(skuAttr);
        orderItemEntity.setSkuQuantity(cartItem.getCount());
        // 优惠信息忽略
        // 积分和成长信息
        orderItemEntity.setGiftGrowth(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());
        orderItemEntity.setGiftIntegration(cartItem.getPrice().multiply(new BigDecimal(cartItem.getCount().toString())).intValue());

        // 订单项的价格信息
        orderItemEntity.setPromotionAmount(new BigDecimal("0"));
        // 优惠券
        orderItemEntity.setCouponAmount(new BigDecimal("0"));
        // 积分优惠
        orderItemEntity.setIntegrationAmount(new BigDecimal("0"));
        // 当前项得实际价格  商品价格*数量
        BigDecimal allPrice = orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity()));
        // 当前商品项目的总额-优惠券-打折-积分优惠 = 实际价格
        BigDecimal finalPrice = allPrice.subtract(orderItemEntity.getCouponAmount()).subtract(orderItemEntity.getPromotionAmount()).subtract(orderItemEntity.getIntegrationAmount());

        orderItemEntity.setRealAmount(finalPrice);

        return orderItemEntity;
    }

    // 构建订单
    private OrderEntity buildOrder(String orderSn) {

        // 补充订单用户id信息
        MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setMemberId(memberResponseVo.getId());
        // 获取本地线程中的数据
        OrderSubmitVo orderSubmitVo = orderSubmitVothreadLocal.get();
        Long addrId = orderSubmitVo.getAddrId();
        R address = memberFeignService.getAddress(addrId);
        MemberAddressVo memberAddressVo = JSONUtil.toBean(address.getData("memberReceiveAddress"), MemberAddressVo.class);

        orderEntity.setOrderSn(orderSn);
        // 随机运费
        // orderEntity.setFreightAmount(new BigDecimal(RandomUtil.randomInt(1,20)));
        // 固定5块运费
        orderEntity.setFreightAmount(new BigDecimal(5));

        // 设置收货人信息
        orderEntity.setReceiverCity(memberAddressVo.getCity());
        orderEntity.setReceiverDetailAddress(memberAddressVo.getDetailAddress());
        orderEntity.setReceiverName(memberAddressVo.getName());
        orderEntity.setReceiverPhone(memberAddressVo.getPhone());
        orderEntity.setReceiverProvince(memberAddressVo.getProvince());
        orderEntity.setReceiverPostCode(memberAddressVo.getPostCode());
        orderEntity.setReceiverRegion(memberAddressVo.getRegion());
        // 设置订单待付款状态
        orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        orderEntity.setCreateTime(new Date());
        orderEntity.setAutoConfirmDay(7); // 订单自动确认时间

        return orderEntity;
    }

    @Override
    public Integer getOrderStatusByOrderSn(String orderSn) {
        OrderEntity orderEntity = getOne(new LambdaQueryWrapper<OrderEntity>().eq(OrderEntity::getOrderSn, orderSn));
        if (orderEntity != null) {
            return orderEntity.getStatus();
        }
        return null;
    }

    @Override
    public void closeOrder(OrderEntity orderEntity) {

        // 关单之前再次确认该订单的最新状态
        OrderEntity byId = getById(orderEntity.getId());
        if (byId.getStatus().equals(OrderStatusEnum.CREATE_NEW.getCode())) {
            byId.setStatus(OrderStatusEnum.CANCLED.getCode());
            updateById(byId);
            // 给mq发消息
            // TODO 解决消息重复问题 可以把接口通过状态字段设置为幂等操作
            //  解决消息丢失/保证消息可靠投递 可以给每一个消息都做好日志记录  然后定期扫描数据库将失败的信息重新执行一遍
            rabbitTemplate.convertAndSend("order-event-exchange", "order.release.other", byId.getOrderSn());
        }
    }

    @Override
    public PayVo getPayByOrderSn(String orderSn) {
        PayVo payVo = new PayVo();
        OrderEntity orderEntity = getOne(new LambdaQueryWrapper<OrderEntity>().eq(OrderEntity::getOrderSn, orderSn));
        BigDecimal amount = orderEntity.getPayAmount().setScale(2, BigDecimal.ROUND_HALF_UP);
        payVo.setTotal_amount(amount.toString());
        String receiverName = orderEntity.getReceiverName() + "_";
        payVo.setSubject(receiverName);
        return payVo;
    }

    @Override
    public PageUtils queryPageWithItem(Map<String, Object> params) {
        MemberResponseVo memberResponseVo = LoginUserInterceptor.loginUser.get();
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
                        .eq("member_id", memberResponseVo.getId())
                        .orderByDesc("id")
        );

        List<OrderEntity> list = page.getRecords().stream().map(orderEntity -> {
            List<OrderItemEntity> orderItemList = orderItemService.list(new LambdaQueryWrapper<OrderItemEntity>().eq(OrderItemEntity::getOrderSn, orderEntity.getOrderSn()));
            orderEntity.setOrderItems(orderItemList);
            return orderEntity;
        }).toList();
        page.setRecords(list);
        return new PageUtils(page);
    }

    @Override
    public String handlePayResult(PayAsyncVo payAsyncVo) {
        //保存交易流水信息
        PaymentInfoEntity paymentInfo = new PaymentInfoEntity();
        paymentInfo.setOrderSn(payAsyncVo.getOut_trade_no());
        paymentInfo.setAlipayTradeNo(payAsyncVo.getTrade_no());
        paymentInfo.setTotalAmount(new BigDecimal(payAsyncVo.getBuyer_pay_amount()));
        paymentInfo.setSubject(payAsyncVo.getBody());
        paymentInfo.setPaymentStatus(payAsyncVo.getTrade_status());
        paymentInfo.setCreateTime(new Date());
        paymentInfo.setCallbackTime(payAsyncVo.getNotify_time());
        //添加到数据库中
        paymentInfoService.save(paymentInfo);

        //修改订单状态
        //获取当前状态
        String tradeStatus = payAsyncVo.getTrade_status();

        if (tradeStatus.equals("TRADE_SUCCESS") || tradeStatus.equals("TRADE_FINISHED")) {
            //支付成功状态
            String orderSn = payAsyncVo.getOut_trade_no(); //获取订单号
            this.updateOrderStatus(orderSn, OrderStatusEnum.PAYED.getCode(), PayConstant.ALIPAY);
        }

        return "success";
    }

    /**
     * 修改订单状态
     *
     * @param orderSn
     * @param code
     */
    private void updateOrderStatus(String orderSn, Integer code, Integer payType) {

        this.baseMapper.updateOrderStatus(orderSn, code, payType);
    }

    @Override
    public void creatSeckillOrder(SeckillOrderTo seckillOrderTo) {
        // 拿消息队列中的数据创建订单
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(seckillOrderTo.getOrderSn());
        orderEntity.setMemberId(seckillOrderTo.getMemberId());
        orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        // 应付价格等于单价*数量
        orderEntity.setPayAmount(seckillOrderTo.getSeckillPrice().multiply(new BigDecimal("" + seckillOrderTo.getNum())));
        this.save(orderEntity);
        // 保存订单项
        OrderItemEntity orderItemEntity = new OrderItemEntity();
        orderItemEntity.setSkuId(seckillOrderTo.getSkuId());
        orderItemEntity.setOrderSn(seckillOrderTo.getOrderSn());
        orderItemEntity.setRealAmount(seckillOrderTo.getSeckillPrice());
        orderItemEntity.setSkuQuantity(seckillOrderTo.getNum());
        // TODO 下面就是重复的拿id远程调接口查商品信息填充,此处直接忽略
        orderItemService.save(orderItemEntity);
        log.info("总之订单创建成功了....");
    }

}